/*
 * Copyright (C) 2025 ameise <ameise.wang@gmail.com> - All Rights Reserved
 *
 * This file is part of e3log.
 *
 * e3log is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * e3log is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with e3log. If not, see <https://www.gnu.org/licenses/>.
 */

package e3log

import (
	"errors"
	"fmt"
	"gitee.com/ameise84/e3lock"
	"github.com/fatih/color"
	"os"
	"path/filepath"
	"time"
)

type Hook func(Level, string)

type logCallBack func(*LogEvent)

func NewLogger(ws ...Writer) *Logger {
	l := Logger{
		enc:           &jsonEncoder{},
		outLevel:      LevelInfo,
		outTimestamp:  true,
		outCaller:     false,
		outErrStack:   true,
		skip:          3,
		fatalExitFunc: os.Exit,
		fatalExitCode: -1,
		timeNowFunc:   time.Now,
	}
	if len(ws) == 0 {
		l.SetWriter(NewConsoleWriter(os.Stdout))
	} else {
		l.SetWriter(ws...)
	}
	return &l
}

type Logger struct {
	mu              e3lock.SpinLock
	enc             iEncoder
	w               Writer
	outLevel        Level // 最低输出日志等级
	outTimestamp    bool
	outCaller       bool // 是否输出 caller
	outErrStack     bool
	skip            int
	hooks           []Hook
	limiter         Limiter
	writeErrHandler func(error, Level, []byte)
	prefix          string // 前缀字段
	fatalExitFunc   func(int)
	fatalExitCode   int
	timeNowFunc     func() time.Time
}

func (ts *Logger) Wait() {
	ts.mu.Lock()
	ts.w.sync()
	ts.mu.Unlock()
}

func (ts *Logger) SetFatalExitFunc(f func(int), code int) {
	ts.fatalExitFunc = f
	ts.fatalExitCode = code
}

func (ts *Logger) SetTimeNowFunc(f func() time.Time) {
	ts.timeNowFunc = f
}

func (ts *Logger) AddWriter(w Writer) {
	if ts.w == nil {
		ts.w = w
		return
	}
	if mw, ok := ts.w.(*multiWriter); ok {
		mw.addWriter(w)
	} else {
		mw = newMultiWriter(ts.w, w)
		ts.w = mw
	}
}

func (ts *Logger) SetWriter(ws ...Writer) {
	switch len(ws) {
	case 0:
		return
	case 1:
		ts.w = ws[0]
	default:
		ts.w = newMultiWriter(ws...)
	}
}

func (ts *Logger) SetLevelByName(v string) error {
	lv, ok := gLevelNameMap[v]
	if !ok {
		return errors.New(fmt.Sprintf("not find [%s] log level", v))
	}
	_gLogger.SetLevel(lv)
	return nil
}

func (ts *Logger) SetLevel(level Level) *Logger {
	ts.outLevel = level
	return ts
}

func (ts *Logger) GetLevel() Level {
	return ts.outLevel
}

func (ts *Logger) CheckLogOut(level Level) bool {
	return ts.outLevel <= level && ts.w != nil
}

func (ts *Logger) SetOutTimestamp(ok bool) *Logger {
	ts.outTimestamp = ok
	return ts
}

func (ts *Logger) SetOutCaller(ok bool) *Logger {
	ts.outCaller = ok
	return ts
}

func (ts *Logger) SetOutErrStack(ok bool) *Logger {
	ts.outErrStack = ok
	return ts
}

func (ts *Logger) ResetHooks(hooks ...Hook) *Logger {
	ts.hooks = hooks
	return ts
}

func (ts *Logger) AddHooks(hooks ...Hook) *Logger {
	ts.hooks = append(ts.hooks, hooks...)
	return ts
}

func (ts *Logger) SetLimiter(limiter Limiter) *Logger {
	ts.limiter = limiter
	return ts
}

func (ts *Logger) SetPrefix(s string) *Logger {
	ts.prefix = s
	return ts
}

func (ts *Logger) ColorMsg(level Level, color *color.Color, msg string) *LogEvent {
	out := false
	switch level {
	case LevelError:
		out = ts.outErrStack
	case LevelFatal:
		out = true
	default:
	}
	return ts.event(ts.skip, level, color, msg, out)
}

func (ts *Logger) Trace(msg string) *LogEvent {
	return ts.event(ts.skip, LevelTrace, nil, msg, false)
}

func (ts *Logger) Debug(msg string) *LogEvent {
	return ts.event(ts.skip, LevelDebug, nil, msg, false)
}

func (ts *Logger) Info(msg string) *LogEvent {
	return ts.event(ts.skip, LevelInfo, nil, msg, false)
}

func (ts *Logger) Warn(msg string) *LogEvent {
	return ts.event(ts.skip, LevelWarn, nil, msg, false)
}

func (ts *Logger) Error(msg string, outStack ...bool) *LogEvent {
	out := ts.outErrStack
	if len(outStack) > 0 {
		out = outStack[0]
	}
	return ts.event(ts.skip, LevelError, nil, msg, out)
}

func (ts *Logger) Fatal(msg string) *LogEvent {
	return ts.event(ts.skip, LevelFatal, nil, msg, true)
}

func (ts *Logger) TracePrintf(format string, args ...any) {
	ts.event(_gLogger.skip+1, LevelTrace, nil, fmt.Sprintf(format, args...), false).Println()
}

func (ts *Logger) DebugPrintf(format string, args ...any) {
	ts.event(_gLogger.skip+1, LevelDebug, nil, fmt.Sprintf(format, args...), false).Println()
}

func (ts *Logger) InfoPrintf(format string, args ...any) {
	ts.event(_gLogger.skip+1, LevelInfo, nil, fmt.Sprintf(format, args...), false).Println()
}

func (ts *Logger) WarnPrintf(format string, args ...any) {
	ts.event(_gLogger.skip+1, LevelWarn, nil, fmt.Sprintf(format, args...), false).Println()
}

func (ts *Logger) ErrorPrintf(format string, args ...any) {
	ts.event(_gLogger.skip+1, LevelError, nil, fmt.Sprintf(format, args...), ts.outErrStack).Println()
}

func (ts *Logger) FatalPrintf(format string, args ...any) {
	ts.event(_gLogger.skip+1, LevelFatal, nil, fmt.Sprintf(format, args...), true).Println()
}

func (ts *Logger) SetSkip(skip int) *Logger {
	ts.skip = skip
	return ts
}

func (ts *Logger) event(callerSkip int, level Level, color *color.Color, msg string, outErrStack bool) *LogEvent {
	if ts.outLevel > level || ts.w == nil {
		return nil
	}
	if ts.limiter != nil {
		if !ts.limiter.allowLog(level) {
			return nil
		}
	}
	evt := takeLogEvent(ts.writeEvent, ts.enc, level, color)
	ts.initField(callerSkip, evt, msg, outErrStack)
	return evt
}

func (ts *Logger) initField(callerSkip int, evt *LogEvent, msg string, outErrStack bool) {
	if ts.outTimestamp {
		t := ts.timeNowFunc()
		evt.Str(gKeyNameStr[LogKeyTime], t.Format(defaultTimeLayout))
	}
	evt.Str(gKeyNameStr[LogKeyLevel], gLevelStr[evt.lv])
	if ts.prefix != "" {
		evt.Str(gKeyNameStr[LogKeyPrefix], ts.prefix)
	}

	getStack := outErrStack
	if getStack || ts.outCaller {
		list := takeStack(callerSkip)
		if ts.outCaller {
			caller := list.Caller()
			caller.File = filepath.Base(caller.File)
			evt.Object(caller)
		}
		if getStack {
			evt.Object(list)
		}
	}
	if len(msg) > 0 {
		evt.Str(gKeyNameStr[LogKeyMsg], msg)
	}
}

func (ts *Logger) writeEvent(evt *LogEvent) {
	ts.mu.Lock()
	ts.w.write(evt.lv, evt.color, evt.buf)
	if ts.hooks != nil {
		s := logBytesToString(evt.buf)
		for _, hook := range ts.hooks {
			hook(evt.lv, s)
		}
	}
	ts.mu.Unlock()
	if evt.lv == LevelFatal && ts.fatalExitFunc != nil {
		ts.fatalExitFunc(ts.fatalExitCode)
	}
	freeLogEvent(evt)
}
